
// BlLua3: Simple Lua interface for TorqueScript


// Includes

#include <Windows.h>

#include "lua.hpp"

#include "BlHooks.cpp"
#include "BlFuncs.cpp"

#include "luainterp.cpp"

// Global variables

lua_State* gL;


// Integration helper functions

// Convert a Lua stack entry into a string for providing to TS functions
// Use static buffer to avoid excessive malloc
#define BLL_ARG_COUNT 20
#define BLL_ARG_MAX 8192
char bll_arg_buffer[BLL_ARG_COUNT+1][BLL_ARG_MAX];
bool bll_toarg(lua_State* L, char* buf, int i, bool err) {
	if       (lua_isnumber(L, i)) {
		double n = lua_tonumber(L, i);
		if(n == (int)n) { snprintf(buf, BLL_ARG_MAX, "%i", (int)n); }
		else            { snprintf(buf, BLL_ARG_MAX, "%g", n     ); }
	} else if(lua_isboolean(L, i)) {
		bool v = lua_toboolean(L, i);
		if(v) { strcpy(buf, "1"); }
		else  { strcpy(buf, "0"); }
	} else if(lua_isstring (L, i)) {
		const char* str = lua_tostring(L, i);
		if(strlen(str)>=BLL_ARG_MAX) {
			if(err) { luaL_error(L, "string arugment too long"); }
			strcpy(buf, "");
		} else { strcpy(buf, str); }
	} else if(lua_isnil    (L, i)) {
		strcpy(buf, "");
	} else {
		if     (lua_istable(L, i)) { if(err) { luaL_error(L, "Attempt to pass table to TS"               ); } }
		else if(lua_isnone (L, i)) { if(err) { luaL_error(L, "Attempt to pass none to TS"                ); } }
		else                       { if(err) { luaL_error(L, "Attempt to pass invalid lua datatype to TS"); } }
		strcpy(buf, "");
		return false;
	}
	return true;
}


// TS -> Lua API

// Display the last Lua error in the BL console
void bll_printError(lua_State* L, const char* operation, const char* item) {
	//error_handler(L);
	BlPrintf("\x03Lua error: %s", lua_tostring(L, -1));
	BlPrintf("\x03    (%s: %s)", operation, item);
	lua_pop(L, 1);
}

bool bll_LuaEval(lua_State* L, const char* str) {
	if(luaL_loadbuffer(L, str, strlen(str), "input") || bll_pcall(L, 0, 1)) {
		bll_printError(L, "eval", str);
		return false;
	}
	return true;
}

// Call a Lua function from TS, return true if success - result will be on Lua stack
bool bll_LuaCall(const char* fname, int argc, const char* argv[]) {
	lua_getglobal(gL, fname);
	for(int i=0; i<argc; i++) {
		lua_pushstring(gL, argv[i]);
	}
	if(bll_pcall(gL, argc, 1)) {
		bll_printError(gL, "call", fname);
		return false;
	}
	return true;
}

const char* bll_ts_luacall(ADDR obj, int argc, const char* argv[]) {
	if(argc<2) return "";
	
	if(!bll_LuaCall(argv[1], argc-2, &argv[2])) { return ""; }
	
	char* retbuf = BlReturnBuffer(BLL_ARG_MAX);
	bll_toarg(gL, retbuf, -1, false); // provide returned value to ts
	lua_pop(gL, 1); // pop returned value
	return retbuf;
}


// Lua -> TS API

// Call a TS function from Lua, push the result to Lua stack
int bll_TsCall(lua_State* L, const char* oname, const char* fname, int argc, int ofs) {
	ADDR obj = (ADDR)NULL;
	if(oname) {
		obj = BlObject(oname);
		if(!obj) { return luaL_error(L, "Object not found"); }
	}
	
	if(argc > BLL_ARG_COUNT) { return luaL_error(L, "Too many arguments to TS call"); }
	
	char* argv[BLL_ARG_COUNT];
	for(int i=0; i<argc; i++) {
		char* argbuf = bll_arg_buffer[i];
		argv[i] = argbuf;
		bll_toarg(L, argbuf, i+ofs+1, true);
	}
	
	// /:^| /
	const char* res;
	if(obj) {
		switch(argc) {
			case  0: res = BlCallObj(obj, fname); break;
			case  1: res = BlCallObj(obj, fname, argv[0]); break;
			case  2: res = BlCallObj(obj, fname, argv[0], argv[1]); break;
			case  3: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2]); break;
			case  4: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3]); break;
			case  5: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4]); break;
			case  6: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); break;
			case  7: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); break;
			case  8: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); break;
			case  9: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8]); break;
			case 10: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9]); break;
			case 11: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10]); break;
			case 12: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11]); break;
			case 13: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12]); break;
			case 14: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13]); break;
			case 15: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14]); break;
			case 16: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15]); break;
			case 17: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16]); break;
			case 18: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17]); break;
			case 19: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18]); break;
			default: res = ""; luaL_error(L, "Call has too many arguments");
		}
	} else {
		switch(argc) {
			case  0: res = BlCall(fname); break;
			case  1: res = BlCall(fname, argv[0]); break;
			case  2: res = BlCall(fname, argv[0], argv[1]); break;
			case  3: res = BlCall(fname, argv[0], argv[1], argv[2]); break;
			case  4: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3]); break;
			case  5: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4]); break;
			case  6: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); break;
			case  7: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); break;
			case  8: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); break;
			case  9: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8]); break;
			case 10: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9]); break;
			case 11: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10]); break;
			case 12: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11]); break;
			case 13: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12]); break;
			case 14: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13]); break;
			case 15: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14]); break;
			case 16: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15]); break;
			case 17: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16]); break;
			case 18: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17]); break;
			case 19: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18]); break;
			default: res = ""; luaL_error(L, "Call has too many arguments");
		}
	}
	
	lua_pushstring(L, res);
	
	return 1;
}

int bll_lua_tscall(lua_State* L) {
	int argc = lua_gettop(L)-1; // number of arguments after function name
	if(argc < 0) return luaL_error(L, "Call must provide a function name");
	
	const char* fname = luaL_checkstring(L, 1);
	
	return bll_TsCall(L, NULL, fname, argc, 1);
}

int bll_lua_tscallobj(lua_State* L) {
	int argc = lua_gettop(L)-2;
	if(argc < 0) return luaL_error(L, "Call must provide a namespace and function name");
	
	const char* oname = luaL_checkstring(L, 1);
	const char* fname = luaL_checkstring(L, 2);
	
	return bll_TsCall(L, oname, fname, argc, 2);
}

int bll_lua_tseval(lua_State* L) {
	const char* str = luaL_checkstring(L, 1);
	const char* res = BlEval(str);
	lua_pushstring(L, res);
	return 1;
}

// Print to BL console - used in Lua print implementation
int bll_lua_tsecho(lua_State* L) {
	const char* str = luaL_checkstring(L, 1);
	BlPrintf("%s", str);
	return 0;
}

const luaL_Reg bll_reg[] = {
	{"call", bll_lua_tscall},
	{"callobj", bll_lua_tscallobj},
	{"eval", bll_lua_tseval},
	{"echo", bll_lua_tsecho},
	{NULL, NULL},
};


// Setup

// Hack to encode the contents of text files as static strings
const char* bll_envSetupStr = (
	#include "env.lua"
);
const char* bll_luaUtilStr = (
	#include "util.lua"
);
const char* bll_tsUtilStr = (
	#include "util.cs"
);

bool init() {
	BlHooksInit();
	BlPrintf("BlLua3: Loading");
	
	BlFuncsInit();
	
	// Setup Lua environment
	gL = lua_open();
	luaL_openlibs(gL);
	// Register TS API to Lua
	luaL_register(gL, "ts", bll_reg);
	// Execute env.lua to setup env
	if(!bll_LuaEval(gL, bll_envSetupStr)) { return false; }
	
	// Expose Lua API to TS
	BlAddFunction(NULL, NULL, "luacall", bll_ts_luacall, "LuaCall(name, ...) - Call Lua function and return result", 2, 20);
	
	// Execute util.lua
	if(!bll_LuaEval(gL, bll_luaUtilStr )) { return false; }
	// Execute util.cs
	BlEval(bll_tsUtilStr);
	
	BlPrintf("BlLua3: init'd");
	return true;
}

bool deinit() {
	lua_close(gL);
	
	BlHooksDeinit();
	BlFuncsDeinit();
	
	BlPrintf("BlLua3: deinit'd");
	return true;
}

bool __stdcall DllMain(HINSTANCE hinstance, DWORD reason, void* reserved) {
	switch(reason) {
		case DLL_PROCESS_ATTACH: return init();
		case DLL_PROCESS_DETACH: return deinit();
		default                : return true;
	}
}
